`define PPU_CTRL        'h00
`define PPU_MASK        'h01
`define PPU_STATUS      'h02
`define PPU_OAM_ADDR    'h03
`define PPU_OAM_DAT     'h04
`define PPU_SCROLL      'h05
`define PPU_ADDR        'h06
`define PPU_DATA        'h07


/*
一些周期性说明
    ppu_rd      采集周期在1.5us (其中低电平是750ns,高电平是750ns)
    ppu_a13     周期也是在1.5us (同上)
    cpu_rw      低电平状态600ns.  下降沿时经过1100ns后(也就是上升沿时500ns后).ppu_rw触发下降沿.(低电平持续200ns)
    ppu_le      高电平持续100ns,低电平持续275ns.低电平持续100ns后.ppu_rw 低电平(持续200ns)
    ppu_cs      低电平持续时间(400ns),当下降沿后960ns(ppu_cs上升沿后560ns)后ppu_rw下降沿



根据nesdev的 https://www.nesdev.org/wiki/PPU_rendering
的模糊说明(基于寄存器)
    与https://www.nesdev.org/w/images/default/4/4f/Ppu.svg 像素时序


0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789        21MHz时钟
|-0-|-1-|-2-|-3-|-4-|-5-|-6-|-7-|-8-|-9-|10-|11-|12-|13-|14-|15-|16-|17-|18-|19-|20-|21-|22-|23-|24-|       像素时钟
//////////////////////   ale与rd的引脚关系.与复制到(th8~th15)16位位移寄存器关系
111111______11______11____          ALE (下降沿持续6个21MHz时钟)
11111111____1111____1111__          RD (下降沿的时候,刚好是下一个像素,持续4个21MHz时钟)
------------T   <   <               th8~th15  ,  <表示右位移
|-0-|-1-|-2-|-3-|-4-|-5-|-6-|-7-|-8-|-9-|10-|11-|12-|13-|14-|15-|16-|17-|18-|19-|20-|21-|22-|23-|24-|
|   N   |   A   |   L   |   M   |   N   |   A   |   L   |   M   |   N   |   A   |   L   |   M   |           ;数据类别
|               |a  | b | c | d | e | f | g | h | a | b | c | d | e | f | g | h |                           ;属性位移 a对应bit0,b对应bit 7
                                  *选择e f渲染


伪代码如
if(rd==0)
    shift_hreg <<= 1;  shift_lreg <<= 1;        ;每个像素都会位移一次
    case (X>>1)&0x3
        0:      获得nt数据.
        1:      获得at数据.
        2:      pat_lbuf = dat;
        3:      dat;            //不变,等待rd上升沿.并且是下一个像素
else if(rd==1)
    shift_hreg <<= 1;  shift_lreg <<= 1;        ;每个像素都会位移一次
    case (X>>1)&0x3
        0:      shift_hreg |= dat<<8; shift_lreg |= pat_lbuf<<8;    //dat是rd==0时,条件为3
        1:
*/

module ppu (
    //ppu接口
    ppu_addr,
    ppu_dat,
    ppu_clk,
    ppu_rw,
    ppu_rd,
    ppu_cs,
    ppu_nmi,

    //cpu接口
    cpu_addr,
    cpu_dat,
    cpu_rw,

    //输出像素
    out_pixel,
    out_x,
    out_y,

    rstn,
);
    output wire[13:0] ppu_addr;
    inout  wire[7:0]  ppu_dat;
    input  wire ppu_clk;
    output reg  ppu_rw = 1'b1;
    output reg  ppu_rd = 1'b1;
    output reg  ppu_nmi = 1'b1;
    input wire ppu_cs;

    //
    input wire[2:0] cpu_addr;
    inout wire[7:0] cpu_dat;
    input wire cpu_rw;

    output wire[5:0] out_pixel;     //0~63

    output wire[8:0] out_x;
    output wire[8:0] out_y;
    input wire rstn;
    assign out_x = hcounter;
    assign out_y = vcounter;

    reg [5:0] pal_ram['h1f:'h00];
    initial begin
        pal_ram['h00] <= 'h22;
        pal_ram['h01] <= 'h29;
        pal_ram['h02] <= 'h1A;
        pal_ram['h03] <= 'h0F;
        pal_ram['h04] <= 'h22;
        pal_ram['h05] <= 'h36;
        pal_ram['h06] <= 'h17;
        pal_ram['h07] <= 'h0F;
        pal_ram['h08] <= 'h22;
        pal_ram['h09] <= 'h30;
        pal_ram['h0a] <= 'h21;
        pal_ram['h0b] <= 'h0F;
        pal_ram['h0c] <= 'h22;
        pal_ram['h0d] <= 'h07;
        pal_ram['h0e] <= 'h17;
        pal_ram['h0f] <= 'h0F;
        pal_ram['h10] <= 'h22;
        pal_ram['h11] <= 'h16;
        pal_ram['h12] <= 'h27;
        pal_ram['h13] <= 'h18;
        pal_ram['h14] <= 'h0F;
        pal_ram['h15] <= 'h1A;
        pal_ram['h16] <= 'h30;
        pal_ram['h17] <= 'h27;
        pal_ram['h18] <= 'h0F;
        pal_ram['h19] <= 'h16;
        pal_ram['h1a] <= 'h30;
        pal_ram['h1b] <= 'h27;
        pal_ram['h1c] <= 'h0F;
        pal_ram['h1d] <= 'h0F;
        pal_ram['h1e] <= 'h36;
        pal_ram['h1f] <= 'h17;
    end


    wire dot_clk = ppu_clk;       //像素时钟

    assign ppu_dat =
        (ppu_rw&~ppu_rd)?8'hzz:                     //ppu_rw 高电平,并且rd为低电平.则为高阻态
        ~ppu_rw?cpu_dat:                            //ppu_rw 为低电平时.直接输出cpu_dat
        8'hzz;
    assign cpu_dat =
            ppu_cs?8'hzz:(cpu_rw?cpu_out_dat:8'hzz);

    reg old_ppu_cs = 1'b1;
    reg[7:0]  cpu_out_dat = 8'd0;
    reg[7:0] last_ppu_dat = 8'd0;

    reg w = 'd0;
    reg[14:0] V = 15'd0;                    //一般为地址线数据
    reg[14:0] T = 15'd0;                    //临时寄存器
    reg [2:0] fine_x = 'd0;                 //卷轴的小X偏移
    reg [8:0] hcounter = 'd256;
    reg [8:0] vcounter = 'd261;

    wire is_rendering = (vcounter<='d240)|((vcounter=='d261)&&(hcounter>='d319));

    reg [7:0] nt_dat = 8'd0;             //命名表数据

    //新的ppu寄存器
    reg [7:0]   bg_lbit_buf = 8'd0;
    reg [15:0]  pat_datL = 16'd0;
    reg [15:0]  pat_datH = 16'd0;
    reg [8:0]   at_bitH = 9'd0;
    reg [8:0]   at_bitL = 9'd0;
    reg [7:0]   at_buf = 8'd0;

    //reg [3:0] color = 'd0;

    reg [7:0] oam['hff:'h00];
    reg [7:0] oam_addr = 8'd0;
    reg [7:0] ppu_ctrl = 8'h10; //synthesis keep;
    reg [7:0] ppu_mask = 8'hff;
    reg [7:0] ppu_status = 8'd0;
    wire[1:0] nametable_sel = ppu_ctrl[1:0];
    wire addr_inc32 = ppu_ctrl[2];
    reg  addr_is_inc = 'b0;
    wire sprite_in1000 = ppu_ctrl[3];
    wire bg_addr = ppu_ctrl[4];
    wire sprite_is8x16 = ppu_ctrl[5];
    wire ppu_mask_sel = ppu_ctrl[6];
    wire ppu_gen_nmi = ppu_ctrl[7];

    initial begin
        ppu_ctrl <= 8'h10;
        ppu_mask <= 8'hff;
    end

    wire ppu_bg_display = 'b1;
    wire ppu_sprite_display = ppu_mask[4];

    wire [3:0] at_att_datH = V[6]?at_buf[7:4]:at_buf[3:0];
    wire [1:0] at_att_datL = ~V[1]?at_att_datH[3:2]:at_att_datH[1:0];
    wire at_bitH8 = at_att_datL[1];
    wire at_bitL8 = at_att_datL[0];

    wire [3:0] color =
        (fine_x==3'd0)?{at_bitH[0],at_bitL[0],pat_datH[0],pat_datL[0]}:          //因为pat_datH在赋值的时候.已经高低位翻转
        (fine_x==3'd1)?{at_bitH[1],at_bitL[1],pat_datH[1],pat_datL[1]}:
        (fine_x==3'd2)?{at_bitH[2],at_bitL[2],pat_datH[2],pat_datL[2]}:
        (fine_x==3'd3)?{at_bitH[3],at_bitL[3],pat_datH[3],pat_datL[3]}:
        (fine_x==3'd4)?{at_bitH[4],at_bitL[4],pat_datH[4],pat_datL[4]}:
        (fine_x==3'd5)?{at_bitH[5],at_bitL[5],pat_datH[5],pat_datL[5]}:
        (fine_x==3'd6)?{at_bitH[6],at_bitL[6],pat_datH[6],pat_datL[6]}:
        (fine_x==3'd7)?{at_bitH[7],at_bitL[7],pat_datH[7],pat_datL[7]}:4'd0
    ;

    assign out_pixel = pal_ram[{1'b0,color}];

    wire[13:0] name_att_addr = 14'h23C0 | {6'h00,V[11:10],V[9:7],V[4:2]};   //
    wire[13:0] pat_addr1 = {1'b0,bg_addr,nt_dat,1'b0,V[14:12]};                       //pat坐标
    wire[13:0] pat_addr2 = {1'b0,bg_addr,nt_dat,1'b1,V[14:12]};


    wire is_render_addr_proc = (((vcounter<'d240)|(vcounter=='d261))&(hcounter<'d256|((hcounter>='d320)&(hcounter<'d338))));

    assign ppu_addr =
    (~ppu_bg_display|(vcounter>=240&&vcounter<261)) ? V[13:0]:         //如果禁用显示bg,或者扫描线在240以后
    is_render_addr_proc?
        (
            //(hcounter[3:1]==3'h00)?V[13:0]:           //第一个周期为nt地址
            (hcounter[2:1]==3'd01)?name_att_addr:       //2.at地址
            (hcounter[2:1]==3'd02)?pat_addr1:           //3.p1地址
            (hcounter[2:1]==3'd03)?pat_addr2:           //4.p2地址
            {2'b10,V[11:0]}                             //1
        ):
    V[13:0]
    ;

    /*
    时钟处理
    |---32----|---33----|3|---00----|---01----|---02----|
    |012345678901234567890|012345678901234567890123456789
    |-NNAALLMmNNAALLMmNNNN|sNNAALLMmNNAALLMmNN
    -------------------------------------------------------
    |-01234567012345670123 456701234567
    | 01234567890123456789 0123456789

        注: N 表示获得nametable数据
            A 表示获得attribute数据
            L 表示获得pattern的低位数据
            M 表示获得pattern的高位数据
            m 表示获得pattern的高位数据.同时增加V的水平地址
            s 奇数帧跳过
    */


    /*
    像素时钟
        341*262*60 = 5360520
        每个时钟间隔约为186.5491ns. 高电平持续时间为93.25ns
    */

    always @(negedge dot_clk) begin
        if(~rstn)begin
            ppu_ctrl <= 8'h10;
            ppu_mask <= 8'hff;
            hcounter <= 'd0;
            vcounter <= 'd0;
        end
        else begin
            if(hcounter<340)begin
                hcounter <= hcounter + 9'd1;
            end
            else begin
                hcounter <= 9'd0;
                if(vcounter<261)begin
                    vcounter <= vcounter + 9'd1;
                end
                else begin
                    vcounter <= 9'd0;
                    hcounter <= 9'd0;
                    T <= 15'd0;
                end
            end
            if(hcounter==100 && vcounter==261)begin
                //$finish;
            end

            ppu_nmi <= vcounter>240;

            //////////////////////////////////////////////
            //ppu读写
            if(~ppu_rw)begin
                //当前为低电平.需要拉高rw
                //ppu写入sram数据
                ppu_rw <= ~ppu_rw;
            end
            else if(~ppu_rd)begin
                //读取状态
                //读取sram数据
                ppu_rd <= ~ppu_rd;
                last_ppu_dat <= ppu_dat;
            end

            if(addr_is_inc)begin
                addr_is_inc <= 'b0;
                V <= V + (addr_inc32?15'h20:15'd1);
            end
            /////////////////////////////////////////////////
            //cpu操作ppu
            if(ppu_cs!=old_ppu_cs)begin
                old_ppu_cs <= ppu_cs;       //保存当前状态
            end
            if(~ppu_cs && (ppu_cs!=old_ppu_cs /*第一次被触发,下一个周期就不会被触发了*/) )begin
                //被选择
                if(~cpu_rw)begin
                    //写入操作
                    case (cpu_addr)
                        `PPU_MASK:ppu_mask <= cpu_dat;
                        `PPU_CTRL:ppu_ctrl <= cpu_dat;
                        `PPU_ADDR:begin
                            if(~w) T[14:8] <= cpu_dat[6:0];      //第一次写入
                            else begin
                                T[7:0]  <= cpu_dat;
                                V <= {T[14:8],cpu_dat};
                                //第二次写入
                            end
                            w <= ~w;
                        end
                        `PPU_DATA:begin
                            //写入数据
                            if(V[13:8]=='h3f)begin
                                pal_ram[V[4:0]] <= cpu_dat[5:0];
                            end
                            else begin
                                ppu_rw <= 'b0;
                            end
                            addr_is_inc <= 'b1;
                        end
                        `PPU_SCROLL:begin
                            if(~w) {T[4:0],fine_x}   <= cpu_dat;
                            else   {T[14:12],T[9:5]} <= cpu_dat;
                        end
                        `PPU_OAM_ADDR:oam_addr <= cpu_dat;
                        `PPU_OAM_DAT:begin
                            oam[oam_addr] <= cpu_dat;
                            oam_addr <= oam_addr + 8'd1;
                        end
                        default:;
                    endcase
                end
                else begin
                    //读取操作
                    case (cpu_addr)
                        `PPU_DATA:begin
                            if(V[13:8]=='h3f)begin
                                last_ppu_dat <= {2'b00,pal_ram[V[4:0]]};
                            end
                            else
                                ppu_rd <= 'b0;
                            cpu_out_dat <=  last_ppu_dat;
                            addr_is_inc <= 'b1;
                        end
                        `PPU_OAM_DAT: begin
                            cpu_out_dat <= oam[oam_addr];
                            oam_addr <= oam_addr + 8'd1;
                        end
                        `PPU_STATUS: begin
                            cpu_out_dat <= ppu_status;
                            ppu_status[7:6] <= 2'b0;    //溢出标志与中断标志清除
                        end
                        default:;
                    endcase
                end
            end
            ///////////////////////////////////////////////////////////////////
            //初始化V
            if(vcounter=='d261)begin
                if(hcounter>='d280&&hcounter<304)begin
                    V <= T;
                end
            end
            //绘制渲染
            if(is_rendering /*&ppu_bg_display*/ )begin
                //bg显示画面
                if((hcounter<256)
                    |((hcounter>=319)&(hcounter<337))
                )begin
`define PAT_DATA_SHIFT \
    pat_datH <= {1'b1,pat_datH[15:1]}; \
    pat_datL <= {1'b1,pat_datL[15:1]}; \
                    //正在渲染
                    at_bitH <= {at_bitH[8],at_bitH[8:1]};     //属性位移器
                    at_bitL <= {at_bitL[8],at_bitL[8:1]};     //
                    if(hcounter[0]=='b1)begin
                        //设置地址并读取
                        //rd为高
                        ppu_rd <= 'b0;

                        // if(hcounter[2:1]==0)begin
                        //     //当前的rd为高电平
                        //     //其实条件是 (x&7)==1
                        //     at_bitH[8] <= at_bitH8;
                        //     at_bitL[8] <= at_bitL8;
                        // end
                        // else begin
                        //     `PAT_DATA_SHIFT;
                        // end

                        case (hcounter[2:1])
                            'd0:begin nt_dat  <= last_ppu_dat; `PAT_DATA_SHIFT; end
                            'd1:begin at_buf  <= last_ppu_dat; `PAT_DATA_SHIFT; end
                            'd2:begin bg_lbit_buf <= last_ppu_dat; `PAT_DATA_SHIFT; end
                            'd3:begin
                                `PAT_DATA_SHIFT;
                            end
                            default:;
                        endcase

                    end
                    else begin
                        ppu_rd <= 'b1;
                        //关闭读取
                        case (hcounter[2:1])
                            'd0:begin
                                pat_datH[7:0] <= pat_datH[8:1];         //只位移1~8
                                pat_datL[7:0] <=  pat_datL[8:1];        //只位移1~8
                                pat_datH[15:8] <= {last_ppu_dat[0],last_ppu_dat[1],last_ppu_dat[2],last_ppu_dat[3],last_ppu_dat[4],last_ppu_dat[5],last_ppu_dat[6],last_ppu_dat[7]};
                                pat_datL[15:8] <= {bg_lbit_buf[0],bg_lbit_buf[1],bg_lbit_buf[2],bg_lbit_buf[3],bg_lbit_buf[4],bg_lbit_buf[5],bg_lbit_buf[6],bg_lbit_buf[7]};
                                //pat_datH[15:8] <= last_ppu_dat;
                                //pat_datL[15:8] <= bg_lbit_buf;
                            end
                            'd1:begin `PAT_DATA_SHIFT;    end
                            'd2:begin `PAT_DATA_SHIFT;    end
                            'd3:begin `PAT_DATA_SHIFT;
                                //
                                {V[10],V[4:0]} <= {V[10],V[4:0]} + 6'd1;
                            end
                            default:;
                        endcase

                    end
                end
                else if(hcounter==256)begin
                    //水平向下移动
                    if(V[14:12]==3'd7)begin
                        V[14:12]<= 3'd0;
                        if(V[9:5]=='h1d)begin
                            V[9:5] <= 5'd0;
                            V[11] <= ~V[11];
                        end
                        else begin
                            //下一个nametable
                            V[9:5] <= V[9:5] + 5'd1;
                        end

                    end
                    else begin
                        //小的偏移
                        V[14:12] <= V[14:12] + 3'd1;
                    end
                end

                if(hcounter<256)begin
                    //绘制像素
                    //第7个像素时,把下一个数据发送过来
                    //fine_x <= fine_x + 3'd1;
                end
                else if(hcounter=='d257)begin
                    {V[10],V[4:0]} <= {T[10],T[4:0]};       //水平初始化
                end
            end
            else if(vcounter==242)begin
                if(hcounter=='d1)begin
                    //设置中断
                    ppu_status[7] <= ppu_gen_nmi;
                end
            end
        end
    end
endmodule

